/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       random_monster.cpp

	$Header: /game/random_monster.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "random_monster.h"

#include "adv_object_type.h"
#include "adventure_map.h"
#include "army.h"
#include "army_ptr.h"
#include "creature_traits.h"
#include "creature_type.h"
#include "enum_operations.h"
#include "object_registration.h"
#include "random.h"
#include "random_monster_experience.h"

namespace
{
	int const k_current_map_format_version = 4;

	t_object_registration<t_random_random_monster> k_registration( k_adv_object_random_random_monster );
	t_object_registration<t_random_monster> k_level_registration( k_adv_object_random_monster );
}

// -------------------------------------------------------------------
// Choose a random creature type using the given parameters
// -------------------------------------------------------------------
static t_creature_type choose_creature( t_town_type		alignment, 
									    bool			ocean,
									    int				max_experience,
										t_creature_type	leader,
										int				min_level, 
										int				max_level )
{
	int				count = 0;
	t_creature_type type;
	t_creature_type	result = k_creature_none;

	for (type = t_creature_type(0); type < k_creature_type_count; enum_incr(type))
	{
		t_creature_traits const& traits = get_traits( type );

		if ( traits.experience > max_experience )
			continue;

		if (leader != k_creature_none && type != leader && traits.experience == max_experience)
			continue;

		if ( (type == k_mermaid || type == k_sea_monster || type == k_pirate) != ocean )
			continue;


		if ( traits.level < min_level || traits.level > max_level )
			continue;
		
		if ( alignment != k_town_none && traits.alignment != alignment )
			continue;

		count++;

		if ( random( count ) == 0 )
			result = type;
	}
	return result;
}

// -------------------------------------------------------------------
// Add some random helper stacks to army. These will be of the same
// alignment as the army leader, at most 1 level lower, and have a
// total experience value of not more than 'experience'.
// -------------------------------------------------------------------
void add_random_helpers( t_army * army, bool is_ocean, int experience )
{
	t_creature_stack const&	leader = army->get_leader();
	t_creature_type			leader_type = leader.get_creature_type();
	int						min_helper_level;
	int						max_helper_level;
	int						max_helper_experience;

	if ( leader_type != k_creature_none )
	{
		int leader_level = get_traits( leader_type ).level;

		min_helper_level = leader_level - 1;
		max_helper_level = leader_level;

		// Don't change leader portrait
		max_helper_experience = minimum( experience, get_traits( leader_type ).experience );
	}
	else
	{
		min_helper_level = 0;
		max_helper_level = 5;
		max_helper_experience = experience;
	}

	t_creature_type type = choose_creature( leader.get_alignment(), is_ocean, 
											max_helper_experience,
											leader_type, 
											min_helper_level, 
											max_helper_level );

	if (type != k_creature_none)
	{
		int creature_value = get_traits( type ).experience;
		int amount = experience / creature_value;
		if (amount > 0)
			army->get_creatures().add( type, amount );
	}
}

// -------------------------------------------------------------------
// t_random_monster_base functions
// -------------------------------------------------------------------

// -------------------------------------------------------------------
// -------------------------------------------------------------------
t_random_monster_base::t_random_monster_base( std::string const& source )
	: t_stationary_adventure_object( source ),
	  m_max_strength( 0 ),
	  m_min_strength( 0 ),
	  m_patrol_type( k_patrol_none ),
	  m_patrol_radius( -1 )
{
}



// -------------------------------------------------------------------
// Read from editor map file
// -------------------------------------------------------------------
bool t_random_monster_base::read_from_map( std::streambuf& buffer, t_progress_handler* )
{
	int format_version = get< t_uint16 >( buffer );
	if ( format_version < 0 || format_version > k_current_map_format_version )
		return false;

	if ( format_version >= 3 && get< t_int8 >( buffer ) != 0 )
	{
		m_patrol_type = static_cast< t_patrol_type >( get< t_int8 >( buffer ) + k_patrol_guard );
		m_patrol_radius = get< t_int8 >( buffer );
	}
	else
	{
		m_patrol_type = k_patrol_guard;
	}
	if (m_patrol_type == k_patrol_guard)
		m_patrol_radius = k_guard_maximum_radius;

	if ( format_version >= 2 )
	{
		if ( !m_events.read_from_map( buffer, 0 ) )
			return false;
	}

	if ( format_version >= 1 )
	{
		if ( !m_linkage_info.read_from_map( buffer ) )
			return false;
	}
	else
	{
		m_linkage_info.reset();
	}

	if ( format_version >= 4 )
	{
		m_min_strength = get< t_uint32 >( buffer );
		if ( m_min_strength < 0 )
			return false;

		m_max_strength = get< t_uint32 >( buffer );
		if ( m_max_strength < m_min_strength || ( m_min_strength > 0 ) != ( m_max_strength > 0 ) )
			return false;
	}

	return true;
}

// -------------------------------------------------------------------
// Place correct monster type on map
// -------------------------------------------------------------------
void t_random_monster_base::place( t_adventure_map& map, t_adv_map_point const& point )
{
	int min_experience;
	int max_experience;

	if ( m_min_strength > 0 )
	{
		assert( m_max_strength >= m_min_strength );
		static int const k_peasant_experience = get_traits( k_peasant ).experience;
		min_experience = m_min_strength * k_peasant_experience;
		max_experience = m_max_strength * k_peasant_experience;
	}
	else
		get_default_experience_range( min_experience, max_experience );

	int					min_level, max_level;
	get_valid_creatue_levels( min_level, max_level );


	int					experience = random( min_experience, max_experience )
									* get_monster_difficulty( map.get_player_difficulty() );
	t_creature_type		type = choose_creature( m_alignment, map.is_ocean( point ), 
												experience, k_creature_none,
												min_level, max_level );

	if (type == k_creature_none)
		return;

	int					creature_value = get_traits( type ).experience;
	t_army_ptr			army;
	int					amount;
	
	if (type == k_sea_monster || type == k_mermaid)
		amount = experience / creature_value;
	else // 2/3rds are the primary creature - rounded up.
		amount = (experience * 2 + creature_value * 3 - 1) / (creature_value * 3);

	if (amount < 1)
		amount = 1;

	if (map.is_ocean( point ) && type != k_sea_monster && type != k_mermaid )
		army = new t_army( m_alignment );
	else
		army = new t_army;
	army->get_creatures().add( type, amount );
	experience -= amount * creature_value;
	if (experience > 0 && type != k_sea_monster && type != k_mermaid )
	{
		add_random_helpers( army, map.is_ocean( point ), experience );
	}
	army->take_events( m_events );
	army->set_patrol_type( m_patrol_type );
	army->set_patrol_radius( m_patrol_radius );
	army->place( map, point );
}


// -------------------------------------------------------------------
// -------------------------------------------------------------------
bool t_random_monster_base::preplacement( t_adventure_map& map, int pass )
{
	t_linkage_data::t_link_alignment link_alignment = m_linkage_info.get_link_alignment( map, pass );

	if ( link_alignment == t_linkage_data::k_link_alignment_wait )
		return true;

	if (!map.guards_can_move() && m_patrol_type == k_patrol_guard)
		m_patrol_radius = 1;

	m_alignment = t_linkage_data::link_alignment_to_town_type( link_alignment );
	return false;
}


// -------------------------------------------------------------------
// t_random_random_monster functions
// -------------------------------------------------------------------

// -------------------------------------------------------------------
// -------------------------------------------------------------------
void t_random_random_monster::get_default_experience_range( int & min, int & max ) const
{
	get_random_monster_experience_range( 3, min, max );
}

// -------------------------------------------------------------------
// Min & max level that can go in this dot
// -------------------------------------------------------------------
void t_random_random_monster::get_valid_creatue_levels( int & min, int & max ) const
{
	min = 0;
	max = 5;
}


// -------------------------------------------------------------------
// t_random_monster functions
// -------------------------------------------------------------------

// -------------------------------------------------------------------
// -------------------------------------------------------------------
void t_random_monster::get_default_experience_range( int & min, int & max ) const
{
	get_random_monster_experience_range( get_creature_level(), min, max );
}

// -------------------------------------------------------------------
// Min & max level that can go in this dot
// -------------------------------------------------------------------
void t_random_monster::get_valid_creatue_levels( int & min, int & max ) const
{
	min = get_creature_level();
	max = get_creature_level();
}

